# 用贪心算法求一个较优解
# 从权重最大的开始，尽可能多排，依次向下找
# 继续调整思路，排班上，先排最难排的夜班，其次排航后，人员上：先排能上少部分班的人，再排普适的人
import numpy as np
import pandas as pd
import copy
import ReadyFile

MyCount = 0
AllProbably = [] #用来记录所有可能的上班表

# 排班算法主要程序
def ScheduledAlgo():
    # 以下导入本次排班的依靠信息
    ScheduledIds_Min = ReadyFile.ScheduledIds_Min #每个班种最少的上班人数
    ScheduledIds_Max = ReadyFile.ScheduledIds_Max #每个班种最多的上班人数
    GayAdaptScheduledIds = ReadyFile.GayAdaptScheduledIds #每个人可以上的班种ID
    GayLikeScheduledScore = ReadyFile.GayLikeScheduledScore #上班人员对每个班种的喜好分数
    GayLikeDayScore = ReadyFile.GayLikeDayScore #上班人员对星期几上班的喜好程度
    GayApply = ReadyFile.GayApply #上班人员的申请
    GayInform = ReadyFile.GayInform # 上班人员信息对应的【字典】
    ScheduledInform = ReadyFile.ScheduledInform # 班中对应的【列表】

    # 拿到当前排班的人数
    GayCount = len(GayAdaptScheduledIds)
    # 拿到每个人可以上班的天数
    GayWorkDayCount = [4]*GayCount
    # 班种个数
    ScheduledIdsCount = 0
    for i1 in range(len(ScheduledIds_Max)):
        ScheduledIdsCount = ScheduledIdsCount + ScheduledIds_Max[i1][i1]
    # 该表装载上班的映射矩阵
    scheduled_list = np.zeros((ScheduledIdsCount,7),dtype=int)+99

    #1、先把所有申请上某班的计划排进去,外面按人进行循环，里面按人的申请进行循环
    for i2 in range(len(GayApply)):
        for i3 in range(7):
            if GayApply[i2][i2][i3]<len(ScheduledInform) and GayApply[i2][i2][i3]>=0:#这条有申请上某班
                # 以下循环定位到i2申请班种的开始列,以下代码才能保障插入到正确位置
                count = 0
                j = 0
                while j < GayApply[i2][i2][i3]:
                    count = count + ScheduledIds_Max[j][j]
                    j = j + 1
                # 找一个还没有申请的班，把i插进去
                for k in range(ScheduledIds_Max[GayApply[i2][i2][i3]][GayApply[i2][i2][i3]]):
                    if scheduled_list[GayApply[i2][i2][i3]][i3] == 99:
                        scheduled_list[count+k][i3] = i2 #id为i2的人申请上一个星期（i3+1）的班，
                        GayWorkDayCount [i2] = GayWorkDayCount [i2] - 1
                        break


    # print(scheduled_list[4:6,:])
    # 2、先排夜班，因为每人每周只能上一次夜班，id为4、5，后一个数字代表上班表要填入的行
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,4,4):
        print("未完成五大队排班！")
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,5,5):
        print("未完成五大队排班！")
    # 3、排五大队
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,11,12):
        print("未完成五大队排班！")
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,11,13):
        print("未完成五大队排班！")
    # 4、排资质岗
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,15,24):
        print("未完成资质岗排班！")
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,15,25):
        print("未完成资质岗排班！")
    # 5、排连线组
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,16,27):
        print("未完成资质岗排班！")
    # 6、排AOC总控
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,6,6):
        print("未完成资质岗排班！")
    # 7、排洪文总控
    if paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,17,29):
        print("未完成资质岗排班！")

    # 递归计算出所有上班的可能
    # DiGuiAlgo(scheduled_list,1,ScheduledWeight)  #参数依次为：0的个数，当前的scheduled_list;每个id可上班的次数
    # global MyCount
    # 把scheduled_list的值解析到Scheduled_list_DF
    Scheduled_list_DF = pd.DataFrame([], columns = ['班种' , '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期七'])
    count = 0
    for i in range(len(ScheduledIds_Max)): # 遍历班种人数上限列表，count为要处理的行，最终一共count行
        for j in range(ScheduledIds_Max[i][i]):# 处理第count行
            ScheduledPlan = [] #插入DF的行
            ScheduledPlan.append(ScheduledInform[i]) #把班种增加到DF中
            for k in range(7):                       # 循环拿到当日上班的人员
                if scheduled_list[count][k] == 99:
                    ScheduledPlan.append(99)
                elif scheduled_list[count][k] in range(len(GayInform)):
                    #print(i,j,scheduled_list[count][k])
                    ScheduledPlan.append(GayInform[scheduled_list[count][k]])
            count = count + 1
            Scheduled_list_DF.loc[count] = ScheduledPlan # 把当日计划加入到DF中
    pd.set_option('display.unicode.ambiguous_as_wide',True)
    pd.set_option('display.unicode.east_asian_width',True)
    pd.set_option('display.width',200)
    print(Scheduled_list_DF)

def DiGuiAlgo(scheduled_list,deep,ScheduledWeight):
    # 为了保障值班的完整性，按天进行贪心算法，保障每个班都有人上
    # 因为都是列表，所以得在新的列表里面操作
    if deep > 7:
        return
        
    deep0 = deep
    scheduled_list0 = []
    scheduled_list0 = copy.deepcopy(scheduled_list)
    # 找权重最大的一个人上周一的第一个班
    for i in range(6):#6个班种
        for j in range(len(list(ScheduledWeight[i].values())[0])):#从当前班种里找一个权重最大的人出来上班
            if deep == 7:
                pass
                #print("j_______________________:",j,"人员id:",list(ScheduledWeight[i].values())[0][j])
            if MyCheck(scheduled_list0,list(ScheduledWeight[i].values())[0][j],deep,i):
                scheduled_list0[i,deep-1] = list(ScheduledWeight[i].values())[0][j]
                # print(i)
                # print(deep)
                # print(list(ScheduledWeight[i].values())[0][j])
                print(scheduled_list0)
                if np.sum(np.where(scheduled_list0,0,1)) == 0:
                    print("i:",i,"+++++++  deep:",deep)
                    print(scheduled_list0)
                break
    
    DiGuiAlgo(scheduled_list0,deep+1,ScheduledWeight)

# 检验将id为i+1的人放到scheduled_list表(42 - ZerosCount)位置是否合适
def MyCheck(scheduled_list,i,deep,WhichClass):
    # 统计当前0的个数
    ZerosCount = np.sum(np.where(scheduled_list[:,deep-1:deep],0,1))
    # i将要放到的位置，即第a行，第b列
    a = 6 - ZerosCount
    b = deep-1
    c = scheduled_list
    #检验这个人本周的上班次数是否超过4
    if np.sum(np.where(c-i,0,1)) > 3:
        return False
    #检验当天这个人是否已有上班
    for k in range(a):
        if scheduled_list[k,b] == i:#当天这个人已经上班了，就返回False
            return False
    #检验这个人前一天是否是航后
    if b >= 1 and (scheduled_list[2,b-1] == i or scheduled_list[3,b-1] == i):#当天这个人已经上班了，就返回False
        return False
    #检验这个人往前两天是否是夜班
    if b >= 2 and (scheduled_list[4,b-2] == i or scheduled_list[5,b-2] == i):#
        return False
    #检验夜班的次数不能大于1
    if (WhichClass == 4 or WhichClass == 5) and np.bincount(scheduled_list[4,:],minlength = i+1)[i] + np.bincount(scheduled_list[5,:],minlength = i+1)[i] > 0:#第五行、第6行的个数之和不能大于1
        return False
    return True

# 检查是否符合夜班的上班规则
def ScheduledCheck(scheduled_list,temp,i,a):# 参数：班表、检查的上班人员id、星期几,班中的ID
    # 本周夜班天数只有1个
    if a == 4 or a == 5:
        if list(scheduled_list[4,:]).count(temp) + list(scheduled_list[5,:]).count(temp)>0:
            return False
        # 夜班后两天没有其他班
        if i < 5:
            if list(scheduled_list[:,i+1]).count(temp) + list(scheduled_list[:,2+1]).count(temp)>1:
                return False
        if i == 5:
            if list(scheduled_list[:,6]).count(temp):
                return False

    # 每周只能上4天班
    if  np.sum(np.where(scheduled_list - temp,0,1)) > 3:
        return False
    return True


# 上班人员对班种a的喜好排名
def GayToSchedulLikeSortFun(GayAdaptScheduledIds,GayLikeScheduledScore,a):
    # 每个人可以上的班种ID/上班人员对每个班种的喜好分数
    a0 = a
    ScheduledLikeSort = []
    for i in range(len(GayAdaptScheduledIds)):
        if a0 in GayAdaptScheduledIds[i][i]:# 对每一个可上这个班种的人进行判断
            b = GayAdaptScheduledIds[i][i].index(a0) # a0在列表中的位置
            if len(ScheduledLikeSort) == 0:# 排序列表为空
                ScheduledLikeSort.append(i)
            else:# 插值
                e = 0
                for j in range(len(ScheduledLikeSort)):
                    c = ScheduledLikeSort[j] # 插入的姓名ID
                    d = GayAdaptScheduledIds[c][c].index(a0) # 该姓名ID对班的喜好分数 所在的位置
                    if GayLikeScheduledScore[i][i][b] > GayLikeScheduledScore[c][c][d]:
                        ScheduledLikeSort.insert(j, i)
                        e = 1
                        break
                if e == 0:
                    ScheduledLikeSort.append(i)

    return ScheduledLikeSort

# 封装的排班函数
def paiban(scheduled_list,GayWorkDayCount,GayAdaptScheduledIds,GayLikeScheduledScore,WhichScheduled,WhichIndex):
    GayToSchedulLikeSort15 = []
    GayToSchedulLikeSort15 = GayToSchedulLikeSortFun(GayAdaptScheduledIds,GayLikeScheduledScore,WhichScheduled)
    if WhichIndex == 29:
        print(GayToSchedulLikeSort15)
    a = WhichIndex
    b = 0
    for i in range(7):
        # 定位到第b列
        b = i % 7
        if scheduled_list[a][b] == 99:
            temp = 0
            for j in range(len(GayToSchedulLikeSort15)):
                if ScheduledCheck(scheduled_list,GayToSchedulLikeSort15[j],i,a):
                    scheduled_list[a][b] = GayToSchedulLikeSort15[j]
                    GayWorkDayCount [GayToSchedulLikeSort15[j]] = GayWorkDayCount [GayToSchedulLikeSort15[j]] - 1
                    temp = 1
                    break
            if temp == 0:
                return False


# 主函数
if __name__=="__main__":
    ScheduledAlgo()